//==========================================================================================
//
//		OpenNETCF.Runtime.InteropServices.MarshalEx
//		Copyright (c) 2003-2004, OpenNETCF.org
//
//		This library is free software; you can redistribute it and/or modify it under 
//		the terms of the OpenNETCF.org Shared Source License.
//
//		This library is distributed in the hope that it will be useful, but 
//		WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
//		FITNESS FOR A PARTICULAR PURPOSE. See the OpenNETCF.org Shared Source License 
//		for more details.
//
//		You should have received a copy of the OpenNETCF.org Shared Source License 
//		along with this library; if not, email licensing@opennetcf.org to request a copy.
//
//		If you wish to contact the OpenNETCF Advisory Board to discuss licensing, please 
//		email licensing@opennetcf.org.
//
//		For general enquiries, email enquiries@opennetcf.org or visit our website at:
//		http://www.opennetcf.org
//
//==========================================================================================
using System;
using System.Text;
using System.Runtime.InteropServices;

namespace OpenNETCF.Runtime.InteropServices 
{
	/// <summary>
	/// Provides a collection of methods for allocating unmanaged memory, copying unmanaged memory blocks, and converting managed to unmanaged types, as well as other miscellaneous methods used when interacting with unmanaged code. 
	/// </summary>
	/// <remarks>For illustrating the control hosting code this class contains only a tiny subset of functionality from the Smart Device Framework.</remarks>
	public sealed class MarshalEx 
	{	
		//don't allow constructor to be called - all static methods
		private MarshalEx(){}

		#region Fields

		private const int GMEM_FIXED = 0x0000;
		private const int LMEM_MOVEABLE = 2;
		private const int LMEM_ZEROINIT  = 0x0040;
		private const int LPTR = (GMEM_FIXED | LMEM_ZEROINIT);
		private static readonly int HIWORDMASK = -65536;//new IntPtr((long)-65536); 

		#endregion

		#region Read functions

		/// <summary>
		/// Allocates a managed <see cref="T:System.String"/> and copies all characters up to the first null character from a string stored in unmanaged memory into it.
		/// <para><b>New in v1.1</b></para>
		/// </summary>
		/// <param name="ptr">The address of the first character.</param>
		/// <returns>A managed string that holds a copy of the unmanaged string.</returns>
		public static string PtrToStringAuto(IntPtr ptr)
		{
			//final string value
			string returnval = "";

			//read first byte
			int firstbyte = Marshal.ReadByte(ptr, 0);
			//read second byte
			int secondbyte = Marshal.ReadByte(ptr, 1);

			//if first byte is non-zero continue
			if(firstbyte!=0)
			{
				
				//if second byte is zero we may have unicode or one byte string
				if(secondbyte==0)
				{
					//read third byte
					int thirdbyte = Marshal.ReadByte(ptr, 2);

					//if third byte is null this is a single byte string
					if(thirdbyte==0)
					{
						//single ascii char
						returnval = ((char)firstbyte).ToString();
					}
					else
					{
						//read unicode
						return Marshal.PtrToStringUni(ptr);
					}
				} 
				else
				{
					//else appears to be ASCII
					return PtrToStringAnsi(ptr);
				}
			}

			return returnval;
		}

		/// <summary>
		/// Copies all characters up to the first null from an unmanaged ANSI string to a managed System.String. Widens each ANSI character to Unicode.
		/// <para><b>New in v1.1</b></para>
		/// </summary>
		/// <param name="ptr">The address of the first character of the unmanaged string.</param>
		/// <returns>A managed <see cref="T:System.String"/> object that holds a copy of the unmanaged ANSI string.</returns>
		public static string PtrToStringAnsi(IntPtr ptr)
		{
			string returnval = "";
			byte thisbyte = 0;
			int offset = 0;

			//while more chars to read
			while(true)
			{
				//read current byte
				thisbyte = Marshal.ReadByte(ptr, offset);

				//if not null
				if(thisbyte == 0)
				{
					break;
				}

				//add the character
				returnval += ((char)thisbyte).ToString();
				
				//move to next position
				offset++;
			}

			return returnval;
		}
		#endregion

		#region Write functions

		#region ANSI String

		/// <summary>
		/// Copies the contents of a managed <see cref="System.String"/> into unmanaged memory, converting into ANSI format as it copies.
		/// </summary>
		/// <param name="s">A managed string to be copied. </param>
		/// <returns>The address, in unmanaged memory, to where s was copied, or 0 if a null reference (Nothing in Visual Basic) string was supplied.</returns>
		public static IntPtr StringToHGlobalAnsi(string s)
		{
			if(s == null)
				return IntPtr.Zero;

			int i = s.Length + 1;
			IntPtr ptr = LocalAlloc(LPTR, (uint)i);

			byte[] data = Encoding.ASCII.GetBytes(s);
			Marshal.Copy(data, 0, ptr, data.Length);

			return ptr;
		}

		#endregion

		#region Unicode String		
		/// <summary>
		/// Copies the contents of a managed <see cref="System.String"/> into unmanaged memory.
		/// </summary>
		/// <param name="s">A managed string to be copied.</param>
		/// <returns>The address, in unmanaged memory, to where s was copied, or 0 if a null reference (Nothing in Visual Basic) string was supplied.</returns>
		public static IntPtr StringToHGlobalUni(string s)
		{
			if(s == null)
				return IntPtr.Zero;
	
			int i = (s.Length + 1) * System.Text.UnicodeEncoding.CharSize;
			
			IntPtr ptr = LocalAlloc(LPTR,(uint)i);

			byte[] data = Encoding.Unicode.GetBytes(s);
			Marshal.Copy(data, 0, ptr, data.Length);

			return ptr;
		}

		#endregion

		#endregion
		

		#region Memory Functions

		private static bool IsNotWin32Atom(IntPtr ptr)
		{
			long b;
			b = (long)ptr;
			return (((long) 0) != (b & (long)MarshalEx.HIWORDMASK)); 
		}

		/// <summary>
		/// Allocates unmanaged memory.
		/// </summary>
		/// <param name="cb">The number of bytes in memory required. </param>
		/// <returns>An IntPtr to the newly allocated memory. This memory must be released using the Marshal.FreeHGlobal method.</returns>
		public static IntPtr AllocHGlobal(int cb)
		{
			IntPtr ptr = LocalAlloc(LPTR, (uint)cb);
			if (ptr == IntPtr.Zero)
			{
				throw new OutOfMemoryException();
 
			}
			return ptr; 
		}

		/// <summary>
		/// Frees memory previously allocated from unmanaged memory.
		/// </summary>
		/// <param name="hGlobal">The handle returned by the original matching call to AllocHGlobal.</param>
		public static void FreeHGlobal(IntPtr hGlobal)
		{
			if(MarshalEx.IsNotWin32Atom(hGlobal))
			{
				LocalFree(hGlobal);
			}
			else 
			{
				throw new ArgumentException("hGlobal is not an unmanaged atom.");
			}
		}

		#endregion


		#region API Prototypes

		[DllImport("coredll.dll",EntryPoint="LocalAlloc",SetLastError=true)]
		static extern IntPtr LocalAlloc(uint uFlags, uint Bytes);

		[DllImport("coredll.dll", EntryPoint="LocalReAlloc", SetLastError=true)]
		static extern IntPtr LocalReAllocCE(IntPtr hMem, int uBytes, int fuFlags);

		[DllImport("coredll.dll",EntryPoint="LocalFree",SetLastError=true)]	
		static extern IntPtr LocalFree(IntPtr hMem);

		#endregion
	}
}
